#include "PinQTask.h"
#include <s32strm.h> // RWriteStream & RReadStream
//#include <txtetext.h> // CPlainText
#include "PinQ.hrh"

const TInt KTimeSecMultiple=1000000;

CPinQTask::CPref::CPref()
	{
	Reset();
	}

CPinQTask::CPref::~CPref()
	{
	delete iName;
	delete iNote;
	}

void CPinQTask::CPref::Reset()
	{
	delete iName;
	iName=NULL;
	delete iNote;
	iNote=NULL;
	iPktCount = PINQDEFPKTCOUNT;
	iPktSize = PINQDEFPKTSIZE;
	iDelay = PINQDEFDELAY;
	iTimeout = PINQDEFTIMEOUT;
	iDnsTimeout = PINQDEFDNSTIMEOUT;
	iLoop = true;
	iEndless = true;
	iCategory = EPinQCategoryUnfiled;
	}

void CPinQTask::CPref::SetNoteL(const TDesC& aText)
	{
	HBufC* temp = aText.AllocL();
	delete iNote;
	iNote = temp;
	}

void CPinQTask::CPref::SetNameL(const TDesC& aName)
	{
	HBufC* temp = aName.AllocL();
	delete iName;
	iName = temp;
	}

void CPinQTask::CPref::ExternalizeL(RWriteStream& aStream) const
	{
	aStream.WriteInt8L(PINQTASKPREFVER);
	aStream.WriteInt32L(iDelay);
	aStream.WriteInt32L(iTimeout);
	aStream.WriteInt32L(iDnsTimeout);
	aStream.WriteInt32L(iPktSize);
	aStream.WriteInt32L(iPktCount);
	aStream.WriteInt8L(iEndless);
	aStream.WriteInt8L(iLoop);
	aStream.WriteInt32L(iCategory);
	if (iName)
		{
		aStream.WriteInt32L(iName->Length());
		aStream << (*iName);
		}
	else
		{
		aStream.WriteInt32L(KErrNotFound);
		}
	if (iNote)
		{
		aStream.WriteInt32L(iNote->Length());
		aStream << (*iNote);
		}
	else
		{
		aStream.WriteInt32L(KErrNotFound);
		}
	}

void CPinQTask::CPref::InternalizeL(RReadStream& aStream)
	{
	TInt ver = aStream.ReadInt8L();
	iDelay = aStream.ReadInt32L();
	iTimeout = aStream.ReadInt32L();
	iDnsTimeout = aStream.ReadInt32L();
	iPktSize = aStream.ReadInt32L();
	iPktCount = aStream.ReadInt32L();
	iEndless = aStream.ReadInt8L();
	iLoop = aStream.ReadInt8L();
	iCategory = aStream.ReadInt32L();
	TInt valueLength = aStream.ReadInt32L();
	if (valueLength != KErrNotFound)
		{
		if (!iName)
			{
			iName = HBufC::NewL(valueLength);
			}
		else
			if (valueLength < iName->Length() )
				{
				iName->ReAlloc(valueLength);
				}

		TPtr ptr(iName->Des());
		aStream >> ptr;
		}
	valueLength = aStream.ReadInt32L();
	if (valueLength != KErrNotFound)
		{
		if (!iNote)
			{
			iNote = HBufC::NewL(valueLength);
			}
		else
			if (valueLength < iNote->Length() )
				{
				iNote->ReAlloc(valueLength);
				}
		TPtr ptr(iNote->Des());
		aStream >> ptr;
		}
	}

CPinQTask::CTimeout::CTimeout(CPinQTask& aTask) :
	CActive(CActive::EPriorityLow), iTask(aTask)
	{
	CActiveScheduler::Add(this);
	}
CPinQTask::CTimeout::~CTimeout()
	{
	iTimer.Close();
	if (IsAdded() )
		Deque(); // calls also Cancel()
	}

void CPinQTask::CTimeout::ConstructL()
	{
	User::LeaveIfError(iTimer.CreateLocal());
	}

void CPinQTask::CTimeout::RunL()
	{
	iTask.OnAlarmL();
	}

void CPinQTask::CTimeout::DoCancel()
	{
	iTimer.Cancel();
	}

void CPinQTask::CTimeout::Start()
	{
	TRequestStatus s=iStatus;
	TRequestStatus s1=iTask.iStatus;
	iTimer.After(iStatus,
			(
				(iTask.iState==ELookingUp)?iTask.Pref().DnsTimeout():iTask.Pref().Timeout()
			)*KTimeSecMultiple);
	ASSERT(!IsActive());
	SetActive();
	}

TInt CPinQTask::CTimeout::RunError(TInt aError)
	{
	Cancel();
	return aError;
	}

CPinQTask* CPinQTask::NewL()
	{
	CPinQTask* self = CPinQTask::NewLC();
	CleanupStack::Pop(self);
	return self;
	}

CPinQTask* CPinQTask::NewLC()
	{
	CPinQTask* self = new (ELeave) CPinQTask;
	CleanupStack::PushL(self);
	self->ConstructL();
	return self;
	}

CPinQTask* CPinQTask::NewL(const CPref& aPref)
	{
	CPinQTask* self = CPinQTask::NewLC(aPref);
	CleanupStack::Pop(self);
	return self;
	}

CPinQTask* CPinQTask::NewLC(const CPinQTask::CPref& aPref)
	{
	CPinQTask* self = new (ELeave) CPinQTask();
	CleanupStack::PushL(self);
	self->ConstructL(aPref);
	return self;
	}

CPinQTask::CPinQTask() :
	CActive(CActive::EPriorityStandard), iTimeout(*this), iReceBufPtr(0, 0, 0)
	{
	iState = EStopped;
	CActiveScheduler::Add(this); // add to AS queue
	}

void CPinQTask::ConstructL()
	{
	//TODO: handle error?
	User::LeaveIfError(iSocketServ.Connect());
	User::LeaveIfError(iTimer.CreateLocal());
	iTimeout.ConstructL();
	}

void CPinQTask::ConstructL(const CPinQTask::CPref& aPref)
	{
	ConstructL();
	iPref.SetNameL(aPref.Name());
	iPref.SetNoteL(aPref.Note());
	iPref.SetCategory(aPref.Category());
	iPref.SetPktSize(aPref.PktSize());
	iPref.SetPktCount(aPref.PktCount());
	iPref.SetDelay(aPref.Delay());
	iPref.SetTimeout(aPref.Timeout());
	iPref.SetEndless(aPref.Endless());
	iPref.SetLoop(aPref.Loop());
	}

CPinQTask::~CPinQTask()
	{
	if (IsAdded() )
		Deque(); // calls also Cancel()
	iSocketServ.Close();
	iResolver.Close();
	iTimer.Close();
	DeletePackets();
	delete iReceBuf;
	delete iSendBuf;
	}

void CPinQTask::StartL(MObserver& aObserver)
	{
	iSeq=0;
	iDnsTime=0;
	iError=ENoError;
	iErrorCode=0;
	DeletePackets();
	iObserver=&aObserver;ASSERT(iCount==0);
	OpenL();
	}
void CPinQTask::SetStateL(TState aState)
	{
	iObserver->TaskStateChangedL(*this, aState);
	iState=aState;
	}

const TDesC& CPinQTask::StateName(CPinQTask::TState aState)
	{
	_LIT(KNone, "inactive ");
	_LIT(KLookingUp,"dns req  ");
	_LIT(KOpenning, "opening  ");
	_LIT(KDealying, "delaying ");
	_LIT(KSending, "sending  ");
	_LIT(KSent, "sent     ");
	_LIT(KReceiving,"receiving");
	_LIT(KRecvd, "received ");
	_LIT(KNA,"N/A");
	switch (aState)
		{
		case EStopped:
			return KNone;
		case ELookingUp:
			return KLookingUp;
		case EOpenning:
			return KOpenning;
		case ESending:
			return KSending;
		case ESent:
			return KSent;
		case EReceiving:
			return KReceiving;
		case EReceived:
			return KRecvd;
		case EDelay:
			return KDealying;
		default:
			return KNA;
		}
	}

void CPinQTask::DoCancel()
	{
	switch (State())
		{
		case EDelay:
			iTimer.Cancel();
			break;
		case ESending:
			if (iError!=ETimeout)
				iTimeout.Cancel();
			iSocket.CancelSend();
			break;
		case EReceiving:
			if (iError!=ETimeout)
				iTimeout.Cancel();
			iSocket.CancelRecv();
			break;
		case ELookingUp:
			if (iError!=ETimeout)
				iTimeout.Cancel();
			iResolver.Cancel();
			iResolver.Close();
			break;
		}
	}
void CPinQTask::RunL()
	{
	switch (iState)
		{
		case ELookingUp:
			iTimeout.Cancel();
			iResolver.Close();
			if (iStatus == KErrNone)
				{
				// DNS look up successful
				TTime time;
				time.HomeTime();
				iDnsTime=time.MicroSecondsFrom(iSentTime).Int64()/1000;
				iNameRecord = iNameEntry();
				// And connect to the IP address
				OpenL(TInetAddr::Cast( iNameRecord.iAddr ).Address() );
				}
			else
				{
				//TODO: Handle error
				iError=CPinQTask::ESystemError;
				iErrorCode=iStatus.Int();
				SetStateL(CPinQTask::EStopped);
				}
			break;
		case EDelay:
		case EOpenning:
			SendToL();
			break;
		case ESending:
			OnSentL();
			break;
		case EReceiving:
			OnRecvdL();
			break;
		default:
			ASSERT(0);
		}
	}

void CPinQTask::OnAlarmL()
	{
	iError=ETimeout;

	if (IsActive())
		Cancel();
	switch (State())
		{
		case ELookingUp:
			SetStateL(EStopped);
			break;
		case EReceiving:
		case ESending:
			CPinQTask::TPacket* packet=LastPacket();
			ASSERT(packet);
			packet->iError=ETimeout;
			NextPingL();
		}
	}

void CPinQTask::SendToL()
	{
	ASSERT(!IsActive());
	iSeq++;ASSERT(Pref().Endless() || Pref().Loop() || iSeq<=Pref().PktCount());ASSERT(iSendBuf);
	iSendBuf->Des()[6]=iSeq>>8; // sequence number
	iSendBuf->Des()[7]=iSeq&0x00FF;
	iError=ENoError;

	TPacket* packet=AddPacket(iSeq, 0);
	if (packet)
		{
		SetStateL(ESending);
		iSocket.SendTo(iSendBuf->Des(), iAddress, 0, iStatus);
		SetActive();
		iTimeout.Start();
		}
	else
		{
		//TODO: hanlde no mem
		}
	}

void CPinQTask::DeletePackets()
	{
	TPacket* p=iFirst;
	while (p)
		{
		TPacket* pN=p->iNext;
		delete p;
		p=pN;
		}
	iFirst=iLast=iPrevious=NULL;
	iCount=0;
	}

CPinQTask::TPacket* CPinQTask::AddPacket(TUint aSeq, TUint aTime)
	{
	TPacket* p=new TPacket;
	if (p)
		{
		if (!iPref.Endless()||iPref.Loop())// qty is limiited
			{
			if (iPref.PktCount()==iCount) //qty hit limit
				{
				if (iPref.Loop())
					{
					//remove the very first
					TPacket* first=iFirst;
					if (first)
						{
						iFirst=iFirst->iNext;// move pointer of the first to the second
						if (first==iLast) // if there was the only packet
							{
							ASSERT(iFirst==NULL);
							iLast=NULL; // set iLast to NULL
							}
						delete first; // delete first packet
						iCount--;
						}
					else
						{
						//ensure no packets
						ASSERT(iCount==0);
						}
					}
				else
					{
					//attempt to add extra packet
					delete p;ASSERT(0);
					}
				}
			}
		if (iFirst)
			{
			//append at the end
			iPrevious=iLast;
			iLast->iNext=p;
			iLast=p;
			}
		else
			{
			//create very first
			iFirst=p;
			iLast=p;
			}
		p->iTask=this;
		p->iSeq=aSeq;
		p->iTime=aTime;
		p->iError=ENoError;
		p->iReceSeq=0;
		p->iNext=NULL;
		iCount++;
		return p;
		}
	else
		{
		return NULL;
		}
	}
void CPinQTask::OnSentL()
	{
	iSentTime.HomeTime();
	iTimeout.Cancel();
	SetStateL(ESent);
	RecvFromL();
	}

void CPinQTask::RecvFromL()
	{
	iError=ENoError;
	SetStateL(EReceiving);ASSERT(iReceBuf);ASSERT(iReceBufPtr==iReceBuf->Des());
	iSocket.RecvFrom(iReceBufPtr, iAddress, 0, iStatus);ASSERT(!IsActive());
	SetActive();
	iTimeout.Start();
	}

void CPinQTask::NextPingL()
	{
	if (Pref().Endless() || Pref().Loop() || (iSeq<Pref().PktCount()))
		{
		if (Pref().Delay())
			{
			ASSERT(!IsActive());
			SetStateL(EDelay);
			iTimer.After(iStatus, Pref().Delay()*KTimeSecMultiple);
			SetActive();
			}
		else
			{
			SendToL();
			}
		}
	else
		{
		ASSERT(iSeq==Pref().PktCount());
		iSocket.Close();
		SetStateL(EStopped);
		}
	}

void CPinQTask::OnRecvdL()
	{
	TUint16 idThis = (((TUint)this) & 0xffff) ^ (((TUint)this) >> 16);
	TUint16 idPckt=(iReceBufPtr[4]<<8)+iReceBufPtr[5];

	if (idThis!=idPckt)
		{
		//TODO: iSocket.CancelRecv();??
		ASSERT(iReceBuf);ASSERT(iReceBuf->Des()==iReceBufPtr);
		iSocket.RecvFrom(iReceBufPtr, iAddress, 0, iStatus);ASSERT(!IsActive());
		SetActive();
		}
	else
		{
		TPacket* packet=LastPacket();
		packet->iReceSeq=(iReceBufPtr[6]<<8)+iReceBufPtr[7];
		TTime time;
		time.HomeTime();
		iTimeout.Cancel();ASSERT(iSeq>0);
		packet->iTime=time.MicroSecondsFrom(iSentTime).Int64()/1000;//? convert to ms
		if (packet->iReceSeq!=iSeq)
			{
			iError=packet->iError=ESeq;
			}
		SetStateL(EReceived);
		NextPingL();
		}
	}

TInt CPinQTask::RunError(TInt aError)
	{
	iErrorCode=aError;
	Cancel();
	iTimeout.Cancel();
	delete iSendBuf;
	iSendBuf=NULL;
	delete iReceBuf;
	iReceBuf=NULL;
	return aError;
	}

void CPinQTask::ExternalizeL(RWriteStream& aStream) const
	{
	aStream.WriteInt8L(PINQTASKVER);
	aStream.WriteInt32L(iUid.iUid);
	iPref.ExternalizeL(aStream);
	}

void CPinQTask::InternalizeL(RReadStream& aStream)
	{
	TInt ver = aStream.ReadInt8L();
	iUid.iUid = aStream.ReadInt32L();
	iPref.InternalizeL(aStream);
	}

void CPinQTask::OpenL()
	{
	ASSERT (iState == EStopped);

	TInetAddr addr;
	if (addr.Input(iPref.Name()) == KErrNone)
		// server name is already a valid ip address
		OpenL(addr.Address());
	else // need to look up name using dns

		{
		iError=ENoError;
		// Initiate DNS
		// TODO: handle error
		SetStateL(ELookingUp);
		int e=iResolver.Open(iSocketServ, KAfInet, KProtocolInetUdp);

		if (0==e)
			{
			// DNS request for name resolution
			iResolver.GetByName(iPref.Name(), iNameEntry, iStatus);ASSERT(!IsActive());
			SetActive();
			iSentTime.HomeTime();//start whatch
			iTimeout.Start();
			}
		else
			{
			//TODO: handle error
			}
		}
	}

void CPinQTask::OpenL(TUint32 aAddr)
	{
	iError=ENoError;
	SetStateL(EOpenning);
	iAddress.SetAddress(aAddr);

	TInt e=iSocket.Open(iSocketServ, KAfInet, KSockDatagram, KProtocolInetIcmp);

	if (e==0)
		{
		// Set up address information

		// Initiate socket connection
		//iSocket.Connect(iAddress, iStatus);

		// Start a timeout
		TUint16 id = (((TUint)this) & 0xffff) ^ (((TUint)this) >> 16);
		const TInt len=8+Pref().PktSize();
		ASSERT(!iSendBuf);ASSERT(!iReceBuf);
		iReceBuf=HBufC8::NewMax(len);ASSERT(iReceBuf);
		//TODO: memory
		iReceBufPtr.Set(iReceBuf->Des());
		//TUint i=iReceBufPtr.Length();
		//TODO: memory
		iSendBuf=HBufC8::NewMax(len);ASSERT(iSendBuf);

		iSendBuf->Des()[0]=0x08; // ICMP type (8 - echo request)
		iSendBuf->Des()[1]=0x00; // ICMP code (0)
		iSendBuf->Des()[2]=0x00; // checksum first byte
		iSendBuf->Des()[3]=0x00; // checksum second byte

		iSendBuf->Des()[4]=id>>8; // identifier
		iSendBuf->Des()[5]=id&0x00FF;

		iSendBuf->Des()[6]=iSeq>>8; // sequence number
		iSendBuf->Des()[7]=iSeq&0x00FF;

		TUint8 c='a';
		for (int i=8; i<len; i++)
			{
			if (c>'z')
				c='a';
			iSendBuf->Des()[i]=c++;
			}

		SendToL();
		}
	else
		{
		//TODO: handle error
		}

	}
// vim: ts=2:sw=2
